package com.sdk.business.impl;

import com.sdk.business.StakeService;
import com.sdk.constant.ErrorCode;
import com.sdk.dto.OfferStakeDto;
import com.sdk.exception.CustomException;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * @author llnf
 */
public class StakeServiceImpl implements StakeService {

  private final Map<String, CopyOnWriteArrayList<OfferStakeDto>> stakeCache;

  public StakeServiceImpl() {
    this.stakeCache = new ConcurrentHashMap<>();
  }

  @Override
  public void offerStake(OfferStakeDto offerStakeDto) {

    // Validate parameters and session
    validateParam(offerStakeDto);
    String betOfferId = offerStakeDto.getBetOfferId();

    stakeCache.compute(betOfferId, (key, list) -> {
      if (list == null) {
        list = new CopyOnWriteArrayList<>();
      }

      // Find the existing OfferStakeDto with the same customerId
      addOfferStakeDtoToList(offerStakeDto, list);

      // Sort the list by stake in descending order and keep only top 20
      sortTop20Result(list);

      return list;
    });
  }

  private void sortTop20Result(CopyOnWriteArrayList<OfferStakeDto> list) {
    list.sort(Comparator.comparingDouble(OfferStakeDto::getStake).reversed());
    if (list.size() > 20) {
      list.subList(20, list.size()).clear();
    }
  }

  private void addOfferStakeDtoToList(OfferStakeDto offerStakeDto,
      List<OfferStakeDto> list) {
    // Find the existing OfferStakeDto with the same customerId
    Optional<OfferStakeDto> existingDtoOptional = list.stream()
        .filter(dto -> dto.getCustomerId().equals(offerStakeDto.getCustomerId()))
        .findFirst();

    if (existingDtoOptional.isPresent()) {
      OfferStakeDto existingDto = existingDtoOptional.get();
      // Replace the existing stake only if the new stake is higher
      if (offerStakeDto.getStake() > existingDto.getStake()) {
        // Remove the existing OfferStakeDto
        list.remove(existingDto);
        // Add the new OfferStakeDto
        list.add(offerStakeDto);
      }
    } else {
      // If no existing OfferStakeDto was found, add the new one
      list.add(offerStakeDto);
    }
  }

  private void validateParam(OfferStakeDto offerStakeDto) {
    if (Objects.isNull(offerStakeDto.getStake())) {
      throw new CustomException(ErrorCode.PARAM_ERROR);
    }
  }

  @Override
  public String listHighStakes(String betOfferId) {

    //Ensure that the write lock thread can re-enter multiple times
    List<OfferStakeDto> betOfferList = stakeCache.get(betOfferId);
    if (betOfferList == null || betOfferList.isEmpty()) {
      return "";
    }
    // Sort the list by stake amount in descending order, and only keep the top 20
    return betOfferList.stream()
        .map(offer -> offer.getCustomerId() + "=" + offer.getStake())
        .collect(Collectors.joining(","));
  }
}
